/**
 * @file hal_usb.c
 * @brief 
 * 
 * Copyright (c) 2021 Bouffalolab team
 * 
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 * 
 */
#include "hal_usb.h"
#include "hal_dma.h"
#include "hal_mtimer.h"
#include "bl702_usb.h"
#include "bl702_glb.h"
#include "bl702_gpio.h"
#include "usb_config.h"

#define MIN(a, b) (((a) < (b)) ? (a) : (b))

#define USB_DC_LOG_WRN(a, ...) bflb_platform_printf(a, ##__VA_ARGS__)
#define USB_DC_LOG_DBG(a, ...)
#define USB_DC_LOG_ERR(a, ...) bflb_platform_printf(a, ##__VA_ARGS__)
#define USB_DC_LOG(a, ...)

usb_dc_device_t usb_fs_device;

static dma_lli_ctrl_t usb_lli_list = 
{
    .src_addr = 0,
    .dst_addr = 0,
    .nextlli = 0,
    .cfg.bits.fix_cnt = 0,
    .cfg.bits.dst_min_mode = 0,
    .cfg.bits.dst_add_mode = 0,
    .cfg.bits.SI = 0,
    .cfg.bits.DI = 0,
    .cfg.bits.SWidth = DMA_TRANSFER_WIDTH_8BIT,
    .cfg.bits.DWidth = DMA_TRANSFER_WIDTH_8BIT,
    .cfg.bits.SBSize = 0,
    .cfg.bits.DBSize = 0,
    .cfg.bits.I = 0,
    .cfg.bits.TransferSize = 0
};

static void usb_set_power_up(void)
{
    uint32_t tmpVal = 0;

    tmpVal = BL_RD_REG(GLB_BASE, GLB_USB_XCVR);
    tmpVal = BL_SET_REG_BIT(tmpVal, GLB_PU_USB);
    BL_WR_REG(GLB_BASE, GLB_USB_XCVR, tmpVal);
}

static void usb_set_power_off(void)
{
    uint32_t tmpVal = 0;

    tmpVal = BL_RD_REG(GLB_BASE, GLB_USB_XCVR);
    tmpVal = BL_CLR_REG_BIT(tmpVal, GLB_PU_USB);
    BL_WR_REG(GLB_BASE, GLB_USB_XCVR, tmpVal);
}

static uint8_t usb_ep_is_enabled(uint8_t ep)
{
    uint8_t ep_idx = USB_EP_GET_IDX(ep);

    /* Check if ep enabled */
    if ((USB_EP_DIR_IS_OUT(ep)) &&
            usb_fs_device.out_ep[ep_idx].ep_ena)
    {
        return 1;
    }
    else if ((USB_EP_DIR_IS_IN(ep)) &&
             usb_fs_device.in_ep[ep_idx].ep_ena)
    {
        return 1;
    }

    return 0;
}

static void usb_xcvr_config(BL_Fun_Type NewState)
{
    uint32_t tmpVal = 0;

    if (NewState != DISABLE)
    {
#if defined(USE_EXTERNAL_TRANSCEIVER)
        tmpVal = BL_RD_REG(GLB_BASE, GLB_USB_XCVR_CONFIG);
        tmpVal = BL_SET_REG_BITS_VAL(tmpVal, GLB_REG_USB_USE_XCVR, 0); //use external tranceiver
        BL_WR_REG(GLB_BASE, GLB_USB_XCVR_CONFIG, tmpVal);
#elif defined(USE_INTERNAL_TRANSCEIVER)
#if 1
        tmpVal = BL_RD_REG(GLB_BASE, GLB_USB_XCVR);
        tmpVal = BL_SET_REG_BITS_VAL(tmpVal, GLB_PU_USB, 1);
        BL_WR_REG(GLB_BASE, GLB_USB_XCVR, tmpVal);

        tmpVal = BL_RD_REG(GLB_BASE, GLB_USB_XCVR);
        tmpVal = BL_SET_REG_BITS_VAL(tmpVal, GLB_USB_SUS, 0);
        tmpVal = BL_SET_REG_BITS_VAL(tmpVal, GLB_USB_SPD, 1); //0 for 1.1 ls,1 for 1.1 fs
        tmpVal = BL_SET_REG_BITS_VAL(tmpVal, GLB_USB_DATA_CONVERT, 0);
        tmpVal = BL_SET_REG_BITS_VAL(tmpVal, GLB_USB_OEB_SEL, 0);
        tmpVal = BL_SET_REG_BITS_VAL(tmpVal, GLB_USB_ROUT_PMOS, 3);
        tmpVal = BL_SET_REG_BITS_VAL(tmpVal, GLB_USB_ROUT_NMOS, 3);
        BL_WR_REG(GLB_BASE, GLB_USB_XCVR, tmpVal);

        tmpVal = BL_RD_REG(GLB_BASE, GLB_USB_XCVR_CONFIG);
        tmpVal = BL_SET_REG_BITS_VAL(tmpVal, GLB_USB_SLEWRATE_P_RISE, 2); //1 for 1.1 ls
        tmpVal = BL_SET_REG_BITS_VAL(tmpVal, GLB_USB_SLEWRATE_P_FALL, 2); //1 for 1.1 ls
        tmpVal = BL_SET_REG_BITS_VAL(tmpVal, GLB_USB_SLEWRATE_M_RISE, 2); //1 for 1.1 ls
        tmpVal = BL_SET_REG_BITS_VAL(tmpVal, GLB_USB_SLEWRATE_M_FALL, 2); //1 for 1.1 ls
        tmpVal = BL_SET_REG_BITS_VAL(tmpVal, GLB_USB_RES_PULLUP_TUNE, 5);
        tmpVal = BL_SET_REG_BITS_VAL(tmpVal, GLB_REG_USB_USE_XCVR, 1);
        tmpVal = BL_SET_REG_BITS_VAL(tmpVal, GLB_USB_BD_VTH, 1);
        tmpVal = BL_SET_REG_BITS_VAL(tmpVal, GLB_USB_V_HYS_P, 2);
        tmpVal = BL_SET_REG_BITS_VAL(tmpVal, GLB_USB_V_HYS_M, 2);
        BL_WR_REG(GLB_BASE, GLB_USB_XCVR_CONFIG, tmpVal);

        ///* force BD=1, not use */
        //tmpVal=BL_RD_REG(GLB_BASE,GLB_USB_XCVR);
        //tmpVal=BL_SET_REG_BIT(tmpVal,GLB_PU_USB_LDO);
        //BL_WR_REG(GLB_BASE,GLB_USB_XCVR,tmpVal);

        /* BD_voltage_thresdhold=2.8V */
        tmpVal = BL_RD_REG(GLB_BASE, GLB_USB_XCVR_CONFIG);
        tmpVal = BL_SET_REG_BITS_VAL(tmpVal, GLB_USB_BD_VTH, 7);
        BL_WR_REG(GLB_BASE, GLB_USB_XCVR_CONFIG, tmpVal);

#else
        tmpVal = BL_RD_REG(GLB_BASE, GLB_USB_XCVR);
        tmpVal = BL_SET_REG_BITS_VAL(tmpVal, GLB_PU_USB, 1);
        BL_WR_REG(GLB_BASE, GLB_USB_XCVR, tmpVal);

        tmpVal = BL_RD_REG(GLB_BASE, GLB_USB_XCVR);
        tmpVal = BL_SET_REG_BITS_VAL(tmpVal, GLB_USB_SUS, 0);
        tmpVal = BL_SET_REG_BITS_VAL(tmpVal, GLB_USB_SPD, 0); //0 for 1.1 ls,1 for 1.1 fs
        tmpVal = BL_SET_REG_BITS_VAL(tmpVal, GLB_USB_DATA_CONVERT, 0);
        tmpVal = BL_SET_REG_BITS_VAL(tmpVal, GLB_USB_OEB_SEL, 0);
        tmpVal = BL_SET_REG_BITS_VAL(tmpVal, GLB_USB_ROUT_PMOS, 3);
        tmpVal = BL_SET_REG_BITS_VAL(tmpVal, GLB_USB_ROUT_NMOS, 3);
        BL_WR_REG(GLB_BASE, GLB_USB_XCVR, tmpVal);

        tmpVal = BL_RD_REG(GLB_BASE, GLB_USB_XCVR_CONFIG);
        tmpVal = BL_SET_REG_BITS_VAL(tmpVal, GLB_USB_SLEWRATE_P_RISE, 1); //4 for 1.1 fs
        tmpVal = BL_SET_REG_BITS_VAL(tmpVal, GLB_USB_SLEWRATE_P_FALL, 1); //3 for 1.1 fs
        tmpVal = BL_SET_REG_BITS_VAL(tmpVal, GLB_USB_SLEWRATE_M_RISE, 1); //4 for 1.1 fs
        tmpVal = BL_SET_REG_BITS_VAL(tmpVal, GLB_USB_SLEWRATE_M_FALL, 1); //3 for 1.1 fs
        tmpVal = BL_SET_REG_BITS_VAL(tmpVal, GLB_USB_RES_PULLUP_TUNE, 5);
        tmpVal = BL_SET_REG_BITS_VAL(tmpVal, GLB_REG_USB_USE_XCVR, 1);
        tmpVal = BL_SET_REG_BITS_VAL(tmpVal, GLB_USB_BD_VTH, 1);
        tmpVal = BL_SET_REG_BITS_VAL(tmpVal, GLB_USB_V_HYS_P, 2);
        tmpVal = BL_SET_REG_BITS_VAL(tmpVal, GLB_USB_V_HYS_M, 2);
        BL_WR_REG(GLB_BASE, GLB_USB_XCVR_CONFIG, tmpVal);
#endif

        tmpVal = BL_RD_REG(GLB_BASE, GLB_USB_XCVR);
        tmpVal = BL_SET_REG_BITS_VAL(tmpVal, GLB_USB_ENUM, 1);
        BL_WR_REG(GLB_BASE, GLB_USB_XCVR, tmpVal);
#endif
    }
    else
    {
#ifdef USE_INTERNAL_TRANSCEIVER
        tmpVal = BL_RD_REG(GLB_BASE, GLB_USB_XCVR);
        tmpVal = BL_SET_REG_BITS_VAL(tmpVal, GLB_USB_ENUM, 0);
        tmpVal = BL_SET_REG_BITS_VAL(tmpVal, GLB_PU_USB, 0);
        BL_WR_REG(GLB_BASE, GLB_USB_XCVR, tmpVal);

        ///* force BD=1, not use */
        //tmpVal=BL_RD_REG(GLB_BASE,GLB_USB_XCVR);
        //tmpVal=BL_SET_REG_BIT(tmpVal,GLB_PU_USB_LDO);
        //BL_WR_REG(GLB_BASE,GLB_USB_XCVR,tmpVal);

        /* BD_voltage_thresdhold=2.8V */
        tmpVal = BL_RD_REG(GLB_BASE, GLB_USB_XCVR_CONFIG);
        tmpVal = BL_SET_REG_BITS_VAL(tmpVal, GLB_USB_BD_VTH, 7);
        BL_WR_REG(GLB_BASE, GLB_USB_XCVR_CONFIG, tmpVal);

#else
        tmpVal = BL_RD_REG(GLB_BASE, GLB_USB_XCVR_CONFIG);
        tmpVal = BL_SET_REG_BITS_VAL(tmpVal, GLB_REG_USB_USE_XCVR, 1); //use internal tranceiver
        BL_WR_REG(GLB_BASE, GLB_USB_XCVR_CONFIG, tmpVal);

        tmpVal = BL_RD_REG(GLB_BASE, GLB_USB_XCVR);
        tmpVal = BL_SET_REG_BITS_VAL(tmpVal, GLB_USB_ENUM, 0);
        tmpVal = BL_SET_REG_BITS_VAL(tmpVal, GLB_PU_USB, 1);
        BL_WR_REG(GLB_BASE, GLB_USB_XCVR, tmpVal);
#endif
    }
}

/**
 * @brief
 *
 * @param dev
 * @param oflag
 * @return int
 */
int usb_open(struct device *dev, uint16_t oflag)
{
    USB_Config_Type usbCfg = {0};

    usb_set_power_off();
    mtimer_delay_ms(10);
    usb_set_power_up();

    usb_xcvr_config(DISABLE);
    usb_xcvr_config(ENABLE);

    NVIC_DisableIRQ(USB_IRQn);

    usbCfg.DeviceAddress = 0;
    usbCfg.EnumInEn = ENABLE;
    usbCfg.EnumOutEn = ENABLE;
    usbCfg.RomBaseDescriptorUsed = 0;
    usbCfg.SoftwareCtrl = 1;
    usbCfg.EnumMaxPacketSize = USB_CTRL_EP_MPS;

    /* Init Device */
    USB_Set_Config(DISABLE, &usbCfg);

    usb_fs_device.out_ep[0].ep_ena = 1U;
    usb_fs_device.in_ep[0].ep_ena = 1U;
    usb_fs_device.out_ep[0].ep_cfg.ep_mps = USB_CTRL_EP_MPS;
    usb_fs_device.out_ep[0].ep_cfg.ep_type = USBD_EP_TYPE_CTRL;
    usb_fs_device.in_ep[0].ep_cfg.ep_mps = USB_CTRL_EP_MPS;
    usb_fs_device.in_ep[0].ep_cfg.ep_type = USBD_EP_TYPE_CTRL;
    
    /* USB interrupt enable config */
    USB_IntEn(USB_INT_ALL, DISABLE);           //all
    USB_IntEn(USB_INT_RESET, ENABLE);          //1
    USB_IntEn(USB_INT_EP0_SETUP_DONE, ENABLE); //5
    USB_IntEn(USB_INT_EP0_IN_DONE, ENABLE);    //7
    USB_IntEn(USB_INT_EP0_OUT_DONE, ENABLE);   //9
    USB_IntEn(USB_INT_RESET_END, ENABLE);      //27

    /* USB interrupt mask config */
    USB_IntMask(USB_INT_ALL, MASK);              //all
    USB_IntMask(USB_INT_RESET, UNMASK);          //1
    USB_IntMask(USB_INT_EP0_SETUP_DONE, UNMASK); //5
    USB_IntMask(USB_INT_EP0_IN_DONE, UNMASK);    //7
    USB_IntMask(USB_INT_EP0_OUT_DONE, UNMASK);   //9
    USB_IntMask(USB_INT_RESET_END, UNMASK);      //27

#ifdef ENABLE_LPM_INT
    USB_IntEn(USB_INT_LPM_PACKET, ENABLE);
    USB_IntEn(USB_INT_LPM_WAKEUP, ENABLE);
    USB_IntMask(USB_INT_LPM_PACKET, UNMASK);
    USB_IntMask(USB_INT_LPM_WAKEUP, UNMASK);

    USB_LPM_Enable();
    USB_Set_LPM_Default_Response(USB_LPM_DEFAULT_RESP_ACK);

#endif

#ifdef ENABLE_SOF3MS_INT
    /* disable sof3ms until reset_end */
    USB_IntEn(USB_INT_LOST_SOF_3_TIMES, DISABLE);
    USB_IntMask(USB_INT_LOST_SOF_3_TIMES, MASK);

    /* recommended enable sof3ms after reset_end */
    USB_IntEn(USB_INT_LOST_SOF_3_TIMES, ENABLE);
    USB_IntMask(USB_INT_LOST_SOF_3_TIMES, UNMASK);
#endif

#ifdef ENABLE_ERROR_INT
    USB_IntEn(USB_INT_ERROR, ENABLE);
    USB_IntMask(USB_INT_ERROR, UNMASK);
#endif
    /*Clear pending interrupts*/
    USB_Clr_IntStatus(USB_INT_ALL);
    
    Interrupt_Handler_Register(USB_IRQn, USB_IRQHandler);
    NVIC_EnableIRQ(USB_IRQn);
    USB_Enable();

    return 0;
}
/**
 * @brief
 *
 * @param dev
 * @return int
 */
int usb_close(struct device *dev)
{
    /* disable all interrupts and force USB reset */
    NVIC_DisableIRQ(USB_IRQn);
    USB_IntMask(USB_INT_LPM_WAKEUP, MASK);
    USB_IntMask(USB_INT_LPM_PACKET, MASK);

    USB_Disable();

    /* clear interrupt status register */
    USB_Clr_IntStatus(USB_INT_ALL);

    usb_set_power_off();

    usb_xcvr_config(DISABLE);
    GLB_Set_USB_CLK(DISABLE);

    return 0;
}
/**
 * @brief
 *
 * @param dev
 * @param cmd
 * @param args
 * @return int
 */
int usb_control(struct device *dev, int cmd, void *args)
{
    struct usb_dc_device *usb_device = (struct usb_dc_device *)dev;
    switch (cmd)
    {
        case DEVICE_CTRL_SET_INT /* constant-expression */:
        {
            uint32_t offset = __builtin_ctz((uint32_t)args);
            while (offset < 24)
            {
                if ((uint32_t)args & (1 << offset))
                {
                    USB_IntEn(offset, ENABLE);
                    USB_IntMask(offset, UNMASK); //11
                    //USB_DC_LOG("offset:%d\r\n",offset);
                }
                offset++;
            }
        }
        break;
        case DEVICE_CTRL_CLR_INT /* constant-expression */:
            /* code */

            break;
        case DEVICE_CTRL_GET_INT /* constant-expression */:
            /* code */
            break;
        case DEVICE_CTRL_CONFIG /* constant-expression */:
            break;
        case DEVICE_CTRL_GET_CONFIG /* constant-expression */:
            break;
        case DEVICE_CTRL_USB_DC_SET_ADDR /* constant-expression */:
            USB_Set_Device_Addr(((uint32_t)args) & 0x7f);
            return 0;
        case DEVICE_CTRL_USB_DC_SET_ACK /* constant-expression */:
            USB_Set_EPx_Status(USB_EP_GET_IDX(((uint32_t)args) & 0x7f), USB_EP_STATUS_ACK);
            return 0;
        case DEVICE_CTRL_USB_DC_SET_NACK /* constant-expression */:
            USB_Set_EPx_Status(USB_EP_GET_IDX(((uint32_t)args) & 0x7f), USB_EP_STATUS_NACK);
            return 0;
        case DEVICE_CTRL_USB_DC_SET_STALL /* constant-expression */:
        {
            uint8_t ep_idx = USB_EP_GET_IDX(((uint32_t)args) & 0x7f);

            if (USB_EP_DIR_IS_OUT(((uint32_t)args) & 0x7f))
            {
                USB_Set_EPx_Status(ep_idx, USB_EP_STATUS_STALL);
                usb_fs_device.out_ep[ep_idx].is_stalled = 1U;
            }
            else
            {
                USB_Set_EPx_Status(ep_idx, USB_EP_STATUS_STALL);
                usb_fs_device.in_ep[ep_idx].is_stalled = 1U;
            }
        }
        return 0;
        case DEVICE_CTRL_USB_DC_CLR_STALL /* constant-expression */:
        {
            uint8_t ep_idx = USB_EP_GET_IDX(((uint32_t)args) & 0x7f);
            if (USB_EP_DIR_IS_OUT(((uint32_t)args) & 0x7f))
            {
                USB_Set_EPx_Status(ep_idx, USB_EP_STATUS_NSTALL);
                usb_fs_device.out_ep[ep_idx].is_stalled = 0;
            }
            else
            {
                USB_Set_EPx_Status(ep_idx, USB_EP_STATUS_NSTALL);
                usb_fs_device.in_ep[ep_idx].is_stalled = 0;
            }
        }
        return 0;
        case DEVICE_CTRL_USB_DC_GET_TX_FIFO_CNT:
            return USB_Get_EPx_TX_FIFO_CNT(((uint32_t)args) & 0x7f);
        case DEVICE_CTRL_USB_DC_GET_RX_FIFO_CNT:
            return USB_Get_EPx_RX_FIFO_CNT(((uint32_t)args) & 0x7f);
        case DEVICE_CTRL_USB_DC_GET_EP_FREE:
            return USB_Is_EPx_RDY_Free(((uint32_t)args) & 0x7f);
        case DEVICE_CTRL_ATTACH_TX_DMA /* constant-expression */:
            usb_device->tx_dma = (struct device *)args;
            break;
        case DEVICE_CTRL_ATTACH_RX_DMA /* constant-expression */:
            usb_device->rx_dma = (struct device *)args;
            break;
        case DEVICE_CTRL_USB_DC_SET_TX_DMA /* constant-expression */:
            USB_Set_EPx_TX_DMA_Interface_Config(((uint32_t)args) & 0x7f,ENABLE);
            break;
        case DEVICE_CTRL_USB_DC_SET_RX_DMA /* constant-expression */:
            USB_Set_EPx_RX_DMA_Interface_Config(((uint32_t)args) & 0x7f,ENABLE);
            break;
        default:
            break;
    }
    return 0;
}

int usb_write(struct device *dev, uint32_t pos, const void *buffer, uint32_t size)
{
    struct usb_dc_device *usb_device = (struct usb_dc_device *)dev;
    uint8_t ep_idx = USB_EP_GET_IDX(pos);

    if(usb_device->in_ep[ep_idx].ep_cfg.ep_type == USBD_EP_TYPE_ISOC)
    {
        uint32_t usb_ep_addr = USB_BASE + 0x308 + ep_idx * 0x10;

        dma_channel_stop(usb_device->tx_dma);
        usb_lli_list.src_addr = (uint32_t)buffer;
        usb_lli_list.dst_addr = usb_ep_addr;
        usb_lli_list.cfg.bits.TransferSize = size;
        usb_lli_list.cfg.bits.DI = 0;
        usb_lli_list.cfg.bits.SI = 1;
        usb_lli_list.cfg.bits.SBSize = DMA_BURST_16BYTE;
        usb_lli_list.cfg.bits.DBSize = DMA_BURST_1BYTE;
        device_control(usb_device->tx_dma,DMA_CHANNEL_UPDATE,(void*)((uint32_t)&usb_lli_list));
        dma_channel_start(usb_device->tx_dma);
    }
    else
    {

    }
    return 0;
}

int usb_read(struct device *dev, uint32_t pos, void *buffer, uint32_t size)
{
    struct usb_dc_device *usb_device = (struct usb_dc_device *)dev;
    uint8_t ep_idx = USB_EP_GET_IDX(pos);

    if(usb_device->out_ep[ep_idx].ep_cfg.ep_type == USBD_EP_TYPE_ISOC)
    {
        uint32_t usb_ep_addr = USB_BASE + 0x308 + ep_idx * 0x1c;

        dma_channel_stop(usb_device->tx_dma);
        usb_lli_list.src_addr = usb_ep_addr;
        usb_lli_list.dst_addr = (uint32_t)buffer;
        usb_lli_list.cfg.bits.TransferSize = size;
        usb_lli_list.cfg.bits.DI = 1;
        usb_lli_list.cfg.bits.SI = 0;
        usb_lli_list.cfg.bits.SBSize = DMA_BURST_1BYTE;
        usb_lli_list.cfg.bits.DBSize = DMA_BURST_16BYTE;
        device_control(usb_device->rx_dma,DMA_CHANNEL_UPDATE,(void*)((uint32_t)&usb_lli_list));
        dma_channel_start(usb_device->rx_dma);
    }
    else
    {
        
    }
    return 0;
}

/**
 * @brief
 *
 * @param index
 * @param name
 * @param flag
 * @return int
 */
int usb_dc_register(enum usb_index_type index, const char *name, uint16_t flag)
{
    struct device *dev;

    if (USB_MAX_INDEX == 0)
        return -DEVICE_EINVAL;

    dev = &(usb_fs_device.parent);

    dev->open = usb_open;
    dev->close = usb_close;
    dev->control = usb_control;
    dev->write = usb_write;
    dev->read = usb_read;

    dev->status = DEVICE_UNREGISTER;
    dev->type = DEVICE_CLASS_USB;
    dev->handle = NULL;

    return device_register(dev, name, flag);
}

/**
 * @brief configure and enable endpoint
 * This function sets endpoint configuration according to one specified in USB
 * endpoint descriptor and then enables it for data transfers.
 *
 * @param dev
 * @param ep_cfg  ep_cfg Endpoint 
 * @return int
 */
int usb_dc_ep_open(struct device *dev, const struct usb_dc_ep_cfg *ep_cfg)
{
    uint8_t ep;
    EP_Config_Type epCfg;

    if (!ep_cfg)
    {
        return -1;
    }
    ep = ep_cfg->ep_addr;

    uint8_t ep_idx = USB_EP_GET_IDX(ep);

    USB_DC_LOG_DBG("%s ep %x, mps %d, type %d\r\n", __func__, ep, ep_cfg->ep_mps, ep_cfg->ep_type);

    if (USB_EP_DIR_IS_OUT(ep))
    {
        epCfg.dir = EP_OUT;
        epCfg.EPMaxPacketSize = ep_cfg->ep_mps;
        usb_fs_device.out_ep[ep_idx].ep_cfg.ep_mps = ep_cfg->ep_mps;
        usb_fs_device.out_ep[ep_idx].ep_cfg.ep_type = ep_cfg->ep_type;
    }
    else
    {
        epCfg.dir = EP_IN;
        epCfg.EPMaxPacketSize = ep_cfg->ep_mps;
        usb_fs_device.in_ep[ep_idx].ep_cfg.ep_mps = ep_cfg->ep_mps;
        usb_fs_device.in_ep[ep_idx].ep_cfg.ep_type = ep_cfg->ep_type;
    }

    if (ep_idx)
    {
        switch (ep_cfg->ep_type)
        {
            case USBD_EP_TYPE_CTRL:
                epCfg.type = USB_DC_EP_TYPE_CTRL;
                break;
            case USBD_EP_TYPE_ISOC:
                epCfg.type = USB_DC_EP_TYPE_ISOC;
                break;
            case USBD_EP_TYPE_BULK:
                epCfg.type = USB_DC_EP_TYPE_BULK;
                break;
            case USBD_EP_TYPE_INTR:
                epCfg.type = USB_DC_EP_TYPE_INTR;
                break;
            default:
                return -1;
        }
        USB_Set_EPx_Config(ep_idx, &epCfg);
        if (USB_EP_DIR_IS_OUT(ep))
        {
            /* Clear NAK and enable ep */
            USB_Set_EPx_Status(USB_EP_GET_IDX(ep), USB_EP_STATUS_ACK);
            usb_fs_device.out_ep[ep_idx].ep_ena = 1U;
        }
        else
        {
            //USB_Set_EPx_Status(USB_EP_GET_IDX(ep), USB_EP_STATUS_ACK);
            USB_Set_EPx_Status(USB_EP_GET_IDX(ep), USB_EP_STATUS_NACK);
            usb_fs_device.in_ep[ep_idx].ep_ena = 1U;
        }
    }
    else
    {
        if (USB_EP_DIR_IS_OUT(ep))
        {
            usb_fs_device.out_ep[ep_idx].ep_ena = 1U;
        }
        else
        {
            usb_fs_device.in_ep[ep_idx].ep_ena = 1U;
        }
    }

    return 0;
}

/**
 * @brief Check if the selected endpoint is stalled
 *
 * @param dev usb device
 * @param[in]  ep       Endpoint address corresponding to the one
 *                      listed in the device configuration table
 * @param[out] stalled  Endpoint stall status
 *
 * @return 0 on success, negative errno code on fail.
 */
int usb_dc_ep_is_stalled(struct device *dev, const uint8_t ep, uint8_t *stalled)
{
    uint8_t ep_idx = USB_EP_GET_IDX(ep);

    if (!stalled)
    {
        return -1;
    }

    *stalled = 0U;
    if (USB_EP_DIR_IS_OUT(ep))
    {
        if (USB_Get_EPx_Status(ep_idx) & USB_EP_STATUS_STALL && usb_fs_device.out_ep[ep_idx].is_stalled)
            *stalled = 1U;
    }
    else
    {
        if (USB_Get_EPx_Status(ep_idx) & USB_EP_STATUS_STALL && usb_fs_device.in_ep[ep_idx].is_stalled)
            *stalled = 1U;
    }

    return 0;
}

/**
 * @brief Write data to the specified endpoint
 *
 * This function is called to write data to the specified endpoint. The
 * supplied usbd_endpoint_callback function will be called when data is transmitted
 * out.
 *
 * @param dev
 * @param[in]  ep        Endpoint address corresponding to the one
 *                       listed in the device configuration table
 * @param[in]  data      Pointer to data to write
 * @param[in]  data_len  Length of the data requested to write. This may
 *                       be zero for a zero length status packet.
 * @param[out] ret_bytes Bytes scheduled for transmission. This value
 *                       may be NULL if the application expects all
 *                       bytes to be written
 *
 * @return 0 on success, negative errno code on fail.
 */
int usb_dc_ep_write(struct device *dev, const uint8_t ep, const uint8_t *data, uint32_t data_len, uint32_t *ret_bytes)
{
    uint8_t ep_idx;
    uint32_t timeout = 0x00FFFFFF;
    uint32_t ep_tx_fifo_addr;

    ep_idx = USB_EP_GET_IDX(ep);
    /* Check if IN ep */
    if (USB_EP_GET_DIR(ep) != USB_EP_DIR_IN)
    {
        return -1;
    }

    /* Check if ep enabled */
    if (!usb_ep_is_enabled(ep))
    {
        return -2;
    }

    while (!USB_Is_EPx_RDY_Free(ep_idx))
    {
        timeout--;
        if (!timeout)
        {
            USB_DC_LOG_ERR("ep%d wait free timeout\r\n", ep);
            return -3;
        }
    }

    // key = irq_lock();
    if (!data)
    {
        if (!data_len)
        {
            /* Zero length packet */
            /* Clear NAK and enable ep */
            USB_Set_EPx_Status(USB_EP_GET_IDX(ep), USB_EP_STATUS_ACK);
            return 0;
        }
        else
        {
            USB_DC_LOG_ERR("data is null\r\n");
            return -4;
        }
    }

    if (data_len > usb_fs_device.in_ep[ep_idx].ep_cfg.ep_mps)
    {
        /* Check if transfer len is too big */
        data_len = usb_fs_device.in_ep[ep_idx].ep_cfg.ep_mps;
    }

    /* Wait for FIFO space available */
    do
    {
        uint32_t avail_space = USB_Get_EPx_TX_FIFO_CNT(ep_idx);
        if (avail_space >= usb_fs_device.in_ep[ep_idx].ep_cfg.ep_mps)
        {
            break;
        }
        //USB_DC_LOG_ERR("EP%d have remain data\r\n", ep_idx);
    }
    while (1);

    /*
     * Write data to FIFO, make sure that we are protected against
     * other USB register accesses.  According to "DesignWare Cores
     * USB 1.1/2.0 Device Subsystem-AHB/VCI Databook": "During FIFO
     * access, the application must not access the UDC/Subsystem
     * registers or vendor registers (for ULPI mode). After starting
     * to access a FIFO, the application must complete the transaction
     * before accessing the register."
     */
    ep_tx_fifo_addr = USB_BASE + USB_EP0_TX_FIFO_WDATA_OFFSET + ep_idx * 0x10;

    if((data_len == 1) && (ep_idx == 0))
    {
        USB_Set_EPx_Xfer_Size(EP_ID0,1);
    }
    else if(ep_idx == 0)
    {
        USB_Set_EPx_Xfer_Size(EP_ID0,64);
    }

    memcopy_to_fifo((void *)ep_tx_fifo_addr, (uint8_t *)data, data_len);
    /* Clear NAK and enable ep */
    USB_Set_EPx_Status(USB_EP_GET_IDX(ep), USB_EP_STATUS_ACK);

    USB_DC_LOG_DBG("EP%d write %u bytes\r\n", ep_idx, data_len);

    if (ret_bytes)
    {
        *ret_bytes = data_len;
    }

    return 0;
}

/**
 * @brief Read data from the specified endpoint
 *
 * This is similar to usb_dc_ep_read, the difference being that, it doesn't
 * clear the endpoint NAKs so that the consumer is not bogged down by further
 * upcalls till he is done with the processing of the data. The caller should
 * reactivate ep by invoking usb_dc_ep_read_continue() do so.
 * 
 * @param dev
 * @param[in]  ep           Endpoint address corresponding to the one
 *                          listed in the device configuration table
 * @param[in]  data         Pointer to data buffer to write to
 * @param[in]  max_data_len Max length of data to read
 * @param[out] read_bytes   Number of bytes read. If data is NULL and
 *                          max_data_len is 0 the number of bytes
 *                          available for read should be returned.
 *
 * @return 0 on success, negative errno code on fail.
 */
int usb_dc_ep_read(struct device *dev, const uint8_t ep, uint8_t *data, uint32_t data_len, uint32_t *read_bytes)
{
    uint8_t ep_idx = USB_EP_GET_IDX(ep);
    uint32_t read_count;
    uint32_t ep_rx_fifo_addr;
    uint32_t timeout = 0x00FFFFFF;
    /* Check if OUT ep */
    if (USB_EP_GET_DIR(ep) != USB_EP_DIR_OUT)
    {
        USB_DC_LOG_ERR("Wrong endpoint direction\r\n");
        return -1;
    }

    /* Check if ep enabled */
    if (!usb_ep_is_enabled(ep))
    {
        USB_DC_LOG_ERR("Not enabled endpoint\r\n");
        return -1;
    }
    /*common process for other ep out*/
    if(ep_idx)
    {
        while (!USB_Is_EPx_RDY_Free(ep_idx))
        {
            timeout--;
            if (!timeout)
            {
                USB_DC_LOG_ERR("ep%d wait free timeout\r\n", ep);
                return -USB_DC_EP_TIMEOUT_ERR;
            }
        }
    }
    /*special process for ep0 out*/
    else if(read_bytes && data_len && (ep_idx == 0))
    {
        while(((BL_RD_WORD(0x4000D800) & (1 << 28)) >> 28))
        {
            timeout--;
            if (!timeout)
            {
                USB_DC_LOG_ERR("ep%d wait free timeout\r\n", ep);
                return -USB_DC_EP_TIMEOUT_ERR;
            }
        }
    }

    /* Allow to read 0 bytes */
    if (!data_len)
    {
        /* Clear NAK and enable ep */
        USB_Set_EPx_Status(USB_EP_GET_IDX(ep), USB_EP_STATUS_ACK);
        return 0;
    }

    read_count = USB_Get_EPx_RX_FIFO_CNT(ep_idx);
    read_count = MIN(read_count, data_len);

    /* Data in the FIFOs is always stored per 8-bit word*/
    ep_rx_fifo_addr = (USB_BASE + USB_EP0_RX_FIFO_RDATA_OFFSET + ep_idx * 0x10);
    fifocopy_to_mem((void *)ep_rx_fifo_addr, data, read_count);
    USB_DC_LOG_DBG("Read EP%d, req %d, read %d bytes\r\n", ep, data_len, read_count);

    if (read_bytes)
    {
        *read_bytes = read_count;
    }

    return 0;
}
/**
 * @brief
 *
 * @param dev
 * @param rb
 * @param ep
 * @return int
 */
int usb_dc_receive_to_ringbuffer(struct device *dev, Ring_Buffer_Type *rb, uint8_t ep)
{
    uint8_t ep_idx;
    uint8_t recv_len;
    uint32_t timeout = 0x00FFFFFF;
    static bool overflow_flag = false;

    /* Check if OUT ep */
    if (USB_EP_GET_DIR(ep) != USB_EP_DIR_OUT)
    {
        USB_DC_LOG_ERR("Wrong endpoint direction\r\n");
        return -USB_DC_EP_DIR_ERR;
    }
    /* Check if ep enabled */
    if (!usb_ep_is_enabled(ep))
    {
        return -USB_DC_EP_EN_ERR;
    }

    ep_idx = USB_EP_GET_IDX(ep);

    while (!USB_Is_EPx_RDY_Free(ep_idx))
    {
        timeout--;
        if (!timeout)
        {
            USB_DC_LOG_ERR("ep%d wait free timeout\r\n", ep);
            return -USB_DC_EP_TIMEOUT_ERR;
        }
    }
    recv_len = USB_Get_EPx_RX_FIFO_CNT(ep_idx);
    
    /*if rx fifo count equal 0,it means last is send nack and ringbuffer is smaller than 64,
    * so,if ringbuffer is larger than 64,set ack to recv next data.
    */
    if(overflow_flag && (Ring_Buffer_Get_Empty_Length(rb)>64) && (!recv_len))
    {
        overflow_flag = false;
        USB_Set_EPx_Rdy(ep_idx);
        return 0;
    }
    else
    {
        uint32_t addr = USB_BASE + 0x11C + (ep_idx - 1) * 0x10;
        Ring_Buffer_Write_Callback(rb, recv_len, fifocopy_to_mem, (void *)addr);

        if(Ring_Buffer_Get_Empty_Length(rb) < 64)
        {
            overflow_flag = true;
            return -USB_DC_RB_SIZE_SMALL_ERR;
        }

        USB_Set_EPx_Rdy(ep_idx);
        return 0;
    }
}
/**
 * @brief
 *
 * @param dev
 * @param rb
 * @param ep
 * @return int
 */
int usb_dc_send_from_ringbuffer(struct device *dev, Ring_Buffer_Type *rb, uint8_t ep)
{
    uint8_t ep_idx;
    uint32_t timeout = 0x00FFFFFF;
    static bool zlp_flag = false;
    static uint32_t send_total_len = 0;

    ep_idx = USB_EP_GET_IDX(ep);

    /* Check if IN ep */
    if (USB_EP_GET_DIR(ep) != USB_EP_DIR_IN)
    {
        return -USB_DC_EP_DIR_ERR;
    }

    /* Check if ep enabled */
    if (!usb_ep_is_enabled(ep))
    {
        return -USB_DC_EP_EN_ERR;
    }
    
    while (!USB_Is_EPx_RDY_Free(ep_idx))
    {
        timeout--;
        if (!timeout)
        {
            USB_DC_LOG_ERR("ep%d wait free timeout\r\n", ep);
            return -USB_DC_EP_TIMEOUT_ERR;
        }
    }

    uint32_t addr = USB_BASE + 0x118 + (ep_idx - 1) * 0x10;

    if(zlp_flag == false)
    {
        if ((USB_Get_EPx_TX_FIFO_CNT(ep_idx) == USB_FS_MAX_PACKET_SIZE) && Ring_Buffer_Get_Length(rb))
        {
            uint32_t actual_len = Ring_Buffer_Read_Callback(rb, USB_FS_MAX_PACKET_SIZE, memcopy_to_fifo, (void *)addr); 
            send_total_len += actual_len;

            if(!Ring_Buffer_Get_Length(rb) && (!(send_total_len % 64)))
            {
                zlp_flag = true;
            }

            USB_Set_EPx_Rdy(ep_idx);
            return 0;
        }
        else
        {
            return -USB_DC_RB_SIZE_SMALL_ERR;
        }
    }
    else
    {
        zlp_flag = false; 
        send_total_len = 0;
        USB_Set_EPx_Rdy(ep_idx);
        return -USB_DC_ZLP_ERR;
    }
}

/**
  * @brief  This function handles PCD interrupt request.
  * @param  hpcd PCD handle
  * @retval HAL status
  */

/**
 * @brief
 *
 * @param device
 */
void usb_dc_isr(usb_dc_device_t *device)
{
    USB_EP_ID epnum = EP_ID0;

    /* EP1_DONE -> EP2_DONE -> ...... -> EP7_DONE*/
    for (USB_INT_Type epint = USB_INT_EP1_DONE; epint <= USB_INT_EP7_DONE; epint += 2)
    {
        if (USB_Get_IntStatus(epint))
        {
            epnum = (epint - USB_INT_EP0_OUT_CMD) >> 1;
            device->parent.callback(&device->parent,(void*)((uint32_t)USB_SET_EP_OUT(epnum)),0,USB_DC_EVENT_EP_OUT_NOTIFY);
            USB_Clr_IntStatus(epint);
            return;
        }
    }

    /* EP1_CMD -> EP2_CMD -> ...... -> EP7_CMD*/
    for (USB_INT_Type epint = USB_INT_EP1_CMD; epint <= USB_INT_EP7_CMD; epint += 2)
    {
        if (USB_Get_IntStatus(epint))
        {
            epnum = (epint - USB_INT_EP0_OUT_CMD) >> 1;
            device->parent.callback(&device->parent,(void*)((uint32_t)USB_SET_EP_IN(epnum)),0,USB_DC_EVENT_EP_IN_NOTIFY);
            USB_Clr_IntStatus(epint);
            return;
        }
    }

    /* reset */
    if (USB_Get_IntStatus(USB_INT_SOF))
    {
        USB_DC_LOG("sof\r\n");
        device->parent.callback(&device->parent,NULL,0,USB_DC_EVENT_SOF);
        USB_Clr_IntStatus(USB_INT_SOF);
        return;
    }

    /* reset */
    if (USB_Get_IntStatus(USB_INT_RESET))
    {
        USB_DC_LOG("reset\r\n");
        device->parent.callback(&device->parent,NULL,0,USB_DC_EVENT_RESET);
        USB_Clr_IntStatus(USB_INT_RESET);
        return;
    }

    /* vbus toggle */
    if (USB_Get_IntStatus(USB_INT_VBUS_TGL))
    {
        USB_DC_LOG("vbus toggle\r\n");
        /*************************************/
        /*************************************/
        USB_Clr_IntStatus(USB_INT_VBUS_TGL);
        return;
    }

    /* EP0 setup done */
    if (USB_Get_IntStatus(USB_INT_EP0_SETUP_DONE))
    {
        USB_DC_LOG("S\r\n");
        device->parent.callback(&device->parent,NULL,0,USB_DC_EVENT_SETUP_NOTIFY);
        USB_Clr_IntStatus(USB_INT_EP0_SETUP_DONE);
        return;
    }

    /* EP0 in done */
    if (USB_Get_IntStatus(USB_INT_EP0_IN_DONE))
    {
        USB_DC_LOG("I\r\n");
        device->parent.callback(&device->parent,(void*)0x80,0,USB_DC_EVENT_EP0_IN_NOTIFY);
        USB_Clr_IntStatus(USB_INT_EP0_IN_DONE);
        return;
    }

    /* EP0 out done */
    if (USB_Get_IntStatus(USB_INT_EP0_OUT_DONE))
    {
        USB_DC_LOG("O\r\n");
        device->parent.callback(&device->parent,(void*)0x00,0,USB_DC_EVENT_EP0_OUT_NOTIFY);
        /*************************************/
        USB_Clr_IntStatus(USB_INT_EP0_OUT_DONE);
        return;
    }

    /* reset end */
    if (USB_Get_IntStatus(USB_INT_RESET_END))
    {
        USB_DC_LOG("re\r\n");
        /*************************************/
        USB_Set_EPx_Rdy(EP_ID0);
        /*************************************/
        USB_Clr_IntStatus(USB_INT_RESET_END);
        return;
    }

    /* LPM wakeup */
    /* usb wakeup interrupt, need update by reg_excel_files */
    if (USB_Get_IntStatus(USB_INT_LPM_WAKEUP))
    {
        /*************************************/
        /*set wInterrupt_Mask global variable*/
        //HAL_PCD_ResumeCallback(hpcd);
        /*************************************/
        USB_Clr_IntStatus(USB_INT_LPM_WAKEUP);
        return;
    }

    /* LPM packet */
    /* usb suspend interrupt, need update by reg_excel_files */
    if (USB_Get_IntStatus(USB_INT_LPM_PACKET))
    {
        /*************************************/
        /* Force low-power mode in the macrocell */
        if (USB_Get_IntStatus(USB_INT_LPM_WAKEUP) == 0)
        {
            //HAL_PCD_SuspendCallback(hpcd);
        }
        /*************************************/
        USB_Clr_IntStatus(USB_INT_LPM_PACKET);
        return;
    }

    /* lost 3 SOF */
    if (USB_Get_IntStatus(USB_INT_LOST_SOF_3_TIMES))
    {
        USB_DC_LOG_ERR("Lost 3 SOFs\r\n");
        /*************************************/
        /*************************************/
        USB_Clr_IntStatus(USB_INT_LOST_SOF_3_TIMES);
        return;
    }
    /* error */
    if (USB_Get_IntStatus(USB_INT_ERROR))
    {
        USB_DC_LOG("USB bus error 0x%08x; EP2 fifo status 0x%08x\r\n", *(volatile uint32_t *)(0x4000D81C), *(volatile uint32_t *)(0x4000D920));
        /*************************************/
        /*************************************/
        device->parent.callback(&device->parent,NULL,0,USB_DC_EVENT_ERROR);
        USB_Clr_IntStatus(USB_INT_ERROR);
        return;
    }
}
/**
 * @brief
 *
 */
void USB_IRQHandler(void)
{
    usb_dc_isr(&usb_fs_device);
}